<?php

/**
 * ApiController class
 *
 * @author Julot
 * @since Thursday, June 9, 2011. 02:58 PM
 */
class ApiController extends Controller {
	/**
	 * Key which has to be in HTTP USERNAME and PASSWORD headers
	 */
	Const APPLICATION_ID = 'AYAMO';

	/**
	 * Default response format
	 * either 'json' or 'xml'
	 */
	private $format = 'json';

	/**
	 * @return array action filters
	 */
	public function filters() {
		return array();
	}

	/**
	 * List models.
	 *
	 * Used by:
	 *   - Get new Cardreq by Mikaze.
	 */
	public function actionIndex() {
		switch ($_GET['model']) {
			case 'cardreq':
				$criteria = new CDbCriteria;
				$criteria->compare('status', Cardreq::STATUS_SENT, false);
				$criteria->compare('dateTime', '>' . $_GET['since']);
				$models = Cardreq::model()->findAll($criteria);
				break;
			case 'news':
				$date = time();
				$month = date('n', $date);
				$day = date('j', $date);
				$year = date('Y', $date);
				$start = mktime(0, 0, 0, $month, $day, $year);
				$end = mktime(23, 59, 59, $month, $day, $year);
				$condition = "hidden = 0 AND dateTime BETWEEN $start AND $end";
				$criteria = array(
					'condition'=>$condition,
					'order'=>"dateTime DESC",
				);
				$models = News::model()->findAll($criteria);
				break;
			default:
				// Model not implemented error
				$t = 'Error: ';
				$t .= 'Mode <b>list</b> is not implemented for model <b>%s</b>';
				$this->_sendResponse(501, sprintf($t, $_GET['model']));
				exit;
		}
		// Did we get some results?
		if (!$models) {
			$this->_sendResponse(200, '[]');
		} else {
			// Prepare response
			$rows = array();
			// We didn't use _getObjectEncoded because this will be array or
			// object.
			foreach ($models as $model) $rows[] = $model->attributes;
			// Send the response
			$this->_sendResponse(200, CJSON::encode($rows));
		}
	}

	/**
	 * Get model.
	 * @param type $id
	 */
	public function actionView($id) {
		switch ($_GET['model']) {
			case 'card':
				// Because card is pseudo model used solely for printing card.
				// id in card is member->code not member->_id
				$model = Card::find($id);
				break;
			default:
				// Model not implemented error
				$t = 'Error: ';
				$t .= 'Mode <b>view</b> is not implemented for model <b>%s</b>';
				$this->_sendResponse(501, sprintf($t, $_GET['model']));
				exit;
		}
		if ($model) {
			if ($model->updateTime == $_GET['lastUpdate']) {
				$response = 'uptodate';
			} else {
				$response = $this->_getObjectEncoded(
					$_GET['model'], $model->attributes
				);
			}
			$this->_sendResponse(200, $response);
		} else {
			$this->_sendResponse(500, 'Model not found.');
		}
		exit;
	}

	protected function error2string($model) {
		$class = get_class($model);
		$msg = "Couldn't create model $class.\r\n";
		foreach ($model->errors as $attribute=>$attr_errors) {
			$msg .= "Attribute: $attribute\r\n";
			foreach ($attr_errors as $attr_error) {
				$msg .= "    $attr_error\r\n";
			}
		}
		return $msg;
	}

	/**
	 * Add data to database using RESTful API by Mikaze.
	 * Import is pseudo model for import data.
	 */
	protected function _createImport() {
		$model = new Member;
		$model->attributes = $_POST['Member'];
		$model->ownerId = 1;
		if (!$model->code) {
			// Try generate code automatically 10 times.
			for ($index = 0; $index < 10; $index++) {
				$model->code = RefMemberCode::generate();
				if ($model->validate()) break;
			}
		}
		$trans = Yii::app()->db->beginTransaction();
		try {
			if (!$model->save()) {
				throw new Exception($this->error2string($model));
			}
			$model->addExpireDate();
			$data = array('id'=>$model->_id, 'code'=>$model->code);

			$classes = array(
				'MemberDate',
				'MemberImage',
				'MemberAddress',
				'MemberIdentity',
				'MemberPhone',
				'MemberIndication',
				'MemberEquipment',
				'MemberOrganization',
			);
			foreach ($classes as $class) {
				if (isset($_POST[$class])) {
					if (isset($_POST[$class][0])) {
						$arrayed = true;
						$total = count($_POST[$class]);
					} else {
						$arrayed = false;
						$total = 1;
					}
					for ($index = 0; $index < $total; $index++) {
						if ($arrayed) {
							$attributes = $_POST[$class][$index];
						} else {
							$attributes = $_POST[$class];
						}
						$child = new $class('import');
						$child->attributes = $attributes;
						if (!$model->addChild($child)) {
							throw new Exception($this->error2string($child));
						}
					}
				}
			}

			$trans->commit();
			$enc = $this->_getObjectEncoded(get_class($model), $data);
			$this->_sendResponse(200, $enc);
		} catch (Exception $e) {
			$trans->rollback();
			$this->_sendResponse(500, $e->getMessage());
		}
	}

	/**
	 * Create model.
	 *
	 * Used by:
	 *   - SMS submit by Karin.
	 *   - Import from excel by Mikaze.
	 */
	public function actionCreate() {
		switch ($_GET['model']) {
			case 'import':
				// Because import is pseudo model used solely for import data.
				$this->_createImport();
				return;
			default:
				$model = $this->_loadModel();
		}
		// Try to assign POST values to attributes
		foreach ($_POST as $var=>$value) {
			// Does the model have this attribute? If not raise an error
			if ($model->hasAttribute($var)) {
				$model->$var = $value;
			} else {
				$msg = 'Parameter <b>%s</b> is not allowed for model <b>%s</b>';
				$this->_sendResponse(500, sprintf($msg, $var, $_GET['model']));
			}
		}
		// Try to save the model
		if ($model->save()) {
			if ($_GET['model'] == 'sms') {
				if (defined('YII_DEBUG') && YII_DEBUG) {
				} else {
					$model->sendNotification();
				}
			}
			$enc = $this->_getObjectEncoded($_GET['model'], $model->attributes);
			$this->_sendResponse(200, $enc);
		} else {
			// Errors occurred
			$msg = "<h1>Error</h1>";
			$msg .= sprintf("Couldn't create model <b>%s</b>", $_GET['model']);
			$msg .= "<ul>";
			foreach ($model->errors as $attribute=>$attr_errors) {
				$msg .= "<li>Attribute: $attribute</li>";
				$msg .= "<ul>";
				foreach ($attr_errors as $attr_error) {
					$msg .= "<li>$attr_error</li>";
				}
				$msg .= "</ul>";
			}
			$msg .= "</ul>";
			$this->_sendResponse(500, $msg);
		}
	}

	private function _loadModel() {
		switch ($_GET['model']) {
			// Get an instance of the respective model
			case 'sms':
				$model = new SmsNews;
				break;
			default:
				$msg = 'Mode <b>create</b> is not implemented for model ' .
					'<b>%s</b>';
				$this->_sendResponse(501, sprintf($msg, $_GET['model']));
				exit;
		}
		return $model;
	}

	private function _sendResponse($status = 200, $body = '',
		$content_type = 'text/html') {
		// set the status
		$status_header = 'HTTP/1.1 ' . $status . ' ' .
			$this->_getStatusCodeMessage($status);
		header($status_header);
		// and the content type
		header('Content-type: ' . $content_type);

		// pages with body are easy
		if ($body != '') {
			// send the body
			echo $body;
			exit;
		}
		// we need to create the body if none is passed
		else {
			// create some body messages
			$message = '';

			// this is purely optional, but makes the pages a little nicer to
			// read for your users.  Since you won't likely send a lot of
			// different status codes, this also shouldn't be too ponderous to
			// maintain
			switch ($status) {
				case 401:
					$message = 'You must be authorized to view this page.';
					break;
				case 404:
					$message = 'The requested URL ' . $_SERVER['REQUEST_URI'] .
						' was not found.';
					break;
				case 500:
					$message = 'The server encountered an error processing ' .
						'your request.';
					break;
				case 501:
					$message = 'The requested method is not implemented.';
					break;
			}

			// servers don't always have a signature turned on
			// (this is an apache directive "ServerSignature On")
			$sig = $_SERVER['SERVER_SIGNATURE'];
			if ($sig == '') {
				$signature = $_SERVER['SERVER_SOFTWARE'] . ' Server at ' .
					$_SERVER['SERVER_NAME'] . ' Port ' .
					$_SERVER['SERVER_PORT'];
			} else $signature = $sig;

			// this should be templated in a real-world solution
			$body = '
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
		"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
	<title>' . $status . ' ' . $this->_getStatusCodeMessage($status) . '</title>
</head>
<body>
	<h1>' . $this->_getStatusCodeMessage($status) . '</h1>
	<p>' . $message . '</p>
	<hr />
	<address>' . $signature . '</address>
</body>
</html>';

			echo $body;
			exit;
		}
	}

	private function _getStatusCodeMessage($status) {
		// these could be stored in a .ini file and loaded
		// via parse_ini_file()... however, this will suffice
		// for an example
		$codes = Array(
			200=>'OK',
			400=>'Bad Request',
			401=>'Unauthorized',
			402=>'Payment Required',
			403=>'Forbidden',
			404=>'Not Found',
			500=>'Internal Server Error',
			501=>'Not Implemented',
		);
		return (isset($codes[$status])) ? $codes[$status] : '';
	}

	/**
	 * Returns the json or xml encoded array
	 *
	 * @param mixed $model
	 * @param mixed $array Data to be encoded
	 * @access private
	 * @return void
	 */
	private function _getObjectEncoded($model, $array) {
		if (isset($_GET['format'])) $this->format = $_GET['format'];

		if ($this->format == 'json') {
			return CJSON::encode($array);
		} elseif ($this->format == 'xml') {
			$result = '<?xml version="1.0">';
			$result .= "\n<$model>\n";
			foreach ($array as $key=>$value)
					$result .= "    <$key>" . utf8_encode($value) . "</$key>\n";
			$result .= '</' . $model . '>';
			return $result;
		} else {
			return;
		}
	}

	/**
	 * Checks if a request is authorized
	 *
	 * @access private
	 * @return void
	 */
	private function _checkAuth() {
		// Check if we have the USERNAME and PASSWORD HTTP headers set?
		$username = 'HTTP_X_' . self::APPLICATION_ID . '_USERNAME';
		$password = 'HTTP_X_' . self::APPLICATION_ID . '_PASSWORD';
		if (!(isset($_SERVER[$username]) and isset($_SERVER[$password]))) {
			// Error: Unauthorized
			$this->_sendResponse(401);
		}
		$username = $_SERVER[$username];
		$password = $_SERVER[$password];
		// Find the user
		$user = User::model()->find('username=?', array(strtolower($username)));
		if ($user === null) {
			// Error: Unauthorized
			$this->_sendResponse(401, 'Error: User Name is invalid');
		} else if (!$user->validatePassword($password)) {
			// Error: Unauthorized
			$this->_sendResponse(401, 'Error: User Password is invalid');
		}
	}

}
